/*
 2-ben definiált maximális számú klienssel összeköttetés létesítése
Ha bármely klienstől “START” parancs érkezik, a szerver “KEZDODIK” üzenetet küld mindkét kliensének,
s a játék elkezdődik. Ha még nincs meg a 2 játékos, erre felhívja a figyelmét a “START”-ot küldő kliensnek,
s vár tovább.
Ha valaki a játék kezdete után akar csatlakozni, a szerver nem engedi, s „A játék már elkezdődött“ üzenettel elküldi.
A játék során a kliensek a következő üzeneteket küldhetik:
„KO“, „PAPIR“, avagy „OLLO“. Más üzenetre a szerver „NEM ERTEM“ választ küld.
Ha egy klines már küldött érvényes üzenetet s újabbat akar küldeni, a szerver „MAR TIPPELTEL“ üzenettel válaszol.
„START“ után 10 mp ideje van mindkét kliensnek tippelni. Ha a 10 mp lejár s valamely kliens még nem tippelt,
ő nem nyerhet. Ha mindkét versenyző tipelt, a szerver eredményt hirdet.
(Kő üti az ollót, olló üti a papírt, papír üti a követ.)
Az eredményről minden klienst értesíteni kell. Az eredményhirdetés után a szerver felteszi a kérdést minden kliensnek,
hogy szerene-e újra játszani. „I“ választ adó kliensek maradnak új játékra,
„N“ választ adókat kilépteti a szerver.
Ekkor újabb „START“ parancsig megint lehetőségük van új klienseknek csatlakozni.
+1 jegy:
A szerver 5 mp-től annál a kliensnél másodpercenként visszaszámlál, aki még nem tippelt.

+ 1 jegy:
A szerver az eredményhirdetéskor küldje ki azt is, milyen IP-vel és port-tal rendelkezik a nyertes kliens.

+ 1 jegy:
Az alap játék módosuljon úgy, hogy ha egy játék már kezdetét vette, az újonnan csatlakozó klienseket ne küldje el a szerver, hanem fork-kal kezdjen nekik új játékot.
 */
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>
#include <time.h>
//más nem kell, itt fordul ennyivel is.


int jatek=0;
struct timeval tv; //ennyi időnként enged tovább a select
struct timeval gamestart; // a játék kezdete
struct timeval elozo; // az előző lehetseges idokuldes idopontja

#define kliensszamMAX 2 // csoportonként a kliensek max száma
#define listenSocketPort "2222" //ezen a porton figyel a szerver

#define handle_error(msg) do { printf("\n"); perror(msg); exit(EXIT_FAILURE); } while (0)

struct timeval t;

struct kliens {
	int cfd; //a kliens socket azonosítója
	char ip[INET6_ADDRSTRLEN]; //a kliens IP, tároljuk, de igazából localhoston ez sosem változik
	char port[6]; //a kliens innen kapcsolódott
	int kliensID; //ezzel azonosítjuk a klienst
	int groupID; //ezzel pedig a forkolás csoportjait
	int PID; // eltároljuk a láncolt listában a PID et is
	int tipp; //ezt tippelte a kliens
	struct kliens* next;
	int op; // osszpont
};

char listenip[INET6_ADDRSTRLEN];
struct kliens* kliensek=NULL;

struct addrinfo kriterium;
struct addrinfo *resultAddressInfo;

char socketInputBuffer[100]; //a kliensektől jövő adat ide kerül

int listenSocket;
int fdmax;
int i;
int kliensszam=0; // a csoportban aktuálisan bent lévő kliensek száma
int group=0; // a forkok által létrehozott csoportok száma

fd_set master;


void serverListen(){
	printf("\nSzerver elindult");
	printf("\nServer PID: %d",getpid()); //kírjuk a PID-et, ehhez kapcsolódnak, aztán a forkkal child processbe kerülnek
	fflush(stdout); // ez kell, mert különben lehet késve ír ki.

	memset(&kriterium,0,sizeof(kriterium));
	kriterium.ai_family= AF_UNSPEC;
	kriterium.ai_socktype=SOCK_STREAM;
	kriterium.ai_flags=AI_PASSIVE;
	getaddrinfo(NULL,listenSocketPort,&kriterium, &resultAddressInfo);

	//a figyelő port ide
	int p = resultAddressInfo->ai_family==AF_INET ?
				ntohs(((struct sockaddr_in*)resultAddressInfo->ai_addr)->sin_port):
				ntohs(((struct sockaddr_in6*)resultAddressInfo->ai_addr)->sin6_port);
	//és IP begyűjtése
	inet_ntop(
			resultAddressInfo->ai_family,
			resultAddressInfo->ai_family==AF_INET ?
					&(((struct sockaddr_in*)resultAddressInfo->ai_addr)->sin_addr):
					&(((struct sockaddr_in6*)resultAddressInfo->ai_addr)->sin6_addr),
			listenip, //IP ide
			resultAddressInfo->ai_family==AF_INET ?
					INET_ADDRSTRLEN:INET6_ADDRSTRLEN
	);

	printf("\nItt akarunk figyelni: %s:%d",listenip,p);
	fflush(stdout);

	//akkor elkezdjük felépíteni a listen-t
	listenSocket=socket(resultAddressInfo->ai_family, resultAddressInfo->ai_socktype, resultAddressInfo->ai_protocol);

	int optionsValue = 1; // a hibamentes listen socket kezeléshez
	setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &optionsValue, sizeof optionsValue);

	if (listenSocket==-1)
		handle_error("socket");

	if(bind(listenSocket,resultAddressInfo->ai_addr,resultAddressInfo->ai_addrlen))
		handle_error("bind");

	if(listen(listenSocket,kliensszamMAX))
		handle_error("listen");

	//oké nincs hiba
	fdmax=listenSocket;
	FD_ZERO(&master);

	FD_SET(listenSocket,&master);
	printf("\nFigyelunk is...");
	fflush(stdout);
}

void msgtoKliens(int k,char* c,int i){
	send(k,c,strlen(c),0);
	char c1[10];
	sprintf(c1,"%d\n",i);
	send(k,c1,strlen(c1),0);
}

void sendallclient(char* c){
	struct kliens* kl=kliensek;
	while(kl!=NULL){
		send(kl->cfd,c,strlen(c),0);
		kl=kl->next;
	}
}

void klienshozzaad(struct sockaddr kliensaddr, int new_fd){
	FD_SET(new_fd,&master);

	if (new_fd>fdmax)
		fdmax=new_fd;

	//új kliens legyártása
	struct kliens* ujKliens=(struct kliens*)malloc(sizeof(struct kliens));

	// a kliens IP és port adatainak előkerítése
	inet_ntop(
			kliensaddr.sa_family,
			kliensaddr.sa_family == AF_INET ?
					&(((struct sockaddr_in*) &kliensaddr)->sin_addr) :
					&(((struct sockaddr_in6*) &kliensaddr)->sin6_addr),
			ujKliens->ip,
			resultAddressInfo->ai_family == AF_INET ?
					INET_ADDRSTRLEN : INET6_ADDRSTRLEN
					);
	int p = kliensaddr.sa_family==AF_INET ?
			ntohs(((struct sockaddr_in*)&kliensaddr)->sin_port):
			ntohs(((struct sockaddr_in6*)&kliensaddr)->sin6_port);
	sprintf(ujKliens->port,"%d",p);
	kliensszam++;

	//a kliens adatok feltöltése
	ujKliens->cfd=new_fd;
	ujKliens->PID=getpid();
	ujKliens->next=kliensek;
	ujKliens->kliensID=kliensszam;
	ujKliens->op=0;

	ujKliens->groupID=group;
	kliensek=ujKliens;
	char c[100]="\033[2J";
	send(ujKliens->cfd,c,strlen(c),0);
	strcpy(c,"\033[0;0f");
	send(ujKliens->cfd,c,strlen(c),0);
	msgtoKliens(ujKliens->cfd,"\ngroupID: ",ujKliens->groupID);
	msgtoKliens(ujKliens->cfd,"\nkliensID: ",ujKliens->kliensID);
	msgtoKliens(ujKliens->cfd,"\nserverPID: ",ujKliens->PID);
	strcpy(c,"\ns: start  k:ko   p:papir   o:ollo\n\n");
	send(ujKliens->cfd,c,strlen(c),0);
	printf("\nkliens kapcsolodott: %s:%s --- %d/%d",ujKliens->ip,ujKliens->port,ujKliens->groupID,ujKliens->kliensID);
	fflush(stdout);
}

void klienstorles(int i){
	FD_CLR(i, &master);
	struct kliens* torlendo=kliensek;
	if(kliensek->cfd==i){
		kliensek=torlendo->next;
		printf("head torles\n");
	}
	else{
		torlendo=kliensek->next;
		struct kliens* prev=kliensek;
		while(torlendo->cfd!=i){
			prev=torlendo;
			torlendo=torlendo->next;
		}
		prev->next=torlendo->next;
	}
	printf("\ntorlendo: %d",torlendo->kliensID);
	fflush(stdout);
	free(torlendo);
	kliensszam--;

	int fdmaxuj=0;
	int j=0;
	for(j=0;j<=fdmax;j++){
		if(FD_ISSET(j,&master)){
			fdmaxuj=j;
		}
	}
	fdmax=fdmaxuj;
	close(i);
}

void idokuldes(int i, int ido){
	char c[200]="";
	//\033[<L>;<C>f

	//Save cursor position:
	//  \033[s
	//- Restore cursor position:
	//  \033[u

	strcpy(c,"\033[s"); //kurzorpozíció megjegyzése
	send(i,c,strlen(c),0);

	strcpy(c,"\033[0;0f"); // 0,0 ba állás
	send(i,c,strlen(c),0);

	snprintf(c, 10,"%d",ido); // hatralevo ido elkuldese
	send(i,c,strlen(c),0);

	strcpy(c,"\033[u"); // kurzor vissza mentett poz-ra
	send(i,c,strlen(c),0);
}

void pontkuldes(int i, int pont){ //azonos az idokuldessel, de messzebb pontot irunk ki
	char c[200]="";

	strcpy(c,"\033[s");
	send(i,c,strlen(c),0);

	strcpy(c,"\033[0;50f");
	send(i,c,strlen(c),0);

	snprintf(c, 10,"%d",pont);
	send(i,c,strlen(c),0);

	strcpy(c,"\033[u");
	send(i,c,strlen(c),0);
}

void timer(){

	struct timeval now; //a pillanatnyi időt tároljuk ebben
	gettimeofday(&now, NULL);
	// ha nem vagyunk jatekban, nem tortenik semmi
	if (!jatek)
		return;

	//ha a idő > 10 sec, azaz game vége, eredményhirdetés
	if ((((now.tv_sec - gamestart.tv_sec)*1000000 + (now.tv_usec - gamestart.tv_usec)>10000000) || (kliensek->tipp>0 && kliensek->next->tipp>0)) && jatek){
		jatek=0;
		eredmenyhirdetes();
		return;
	}

	// az előző időtől ennyi telt el.
	long usec_diff = (now.tv_sec - elozo.tv_sec)*1000000 + (now.tv_usec - elozo.tv_usec);



	// ha ez a diff kevesebb mint 1 mp, nem teszünk semmit
	//ez azert kell, hogy a kliens pl beir egy tippet, akkor ne ugorjon egyet az ido visszaszamlalas.
	if (usec_diff<1000000)
		return;

	gettimeofday(&elozo, NULL);

	// itt kuldjuk el a meg nem tippelt kliensnek, hogy ido van (10-bol kivonjuk a kulonbseget)
	struct kliens* kl=kliensek;
	int t=(int)((now.tv_sec - gamestart.tv_sec)*1000000 + (now.tv_usec - gamestart.tv_usec))/1000000;
	while(kl!=NULL){
		if ((kl->tipp == 0) && t>2 ){ // 8 mp től szamolunk vissza
			int i=kl->cfd;
			idokuldes(i,10-t);
		}
		kl=kl->next;
	}
}

void eredmenyhirdetes(){
	if (kliensek==NULL)
		return;
	if (kliensek->next==NULL)
			return;

	char c1[200]=""; // ez lesz a küldendő eredmény
	char c2[200]=""; // ez lesz a küldendő eredmény
	// kinek erősebb a tippje???
	if	(
			(kliensek->tipp==1 && kliensek->next->tipp==2) || // next elég, mert ketten vagyunk csak
			(kliensek->tipp==2 && kliensek->next->tipp==3) ||
			(kliensek->tipp==3 && kliensek->next->tipp==1)
		){
		strcpy(c1,"\nNem nyert.\n");
		strcpy(c2,"\nNyert!\n");
		kliensek->next->op++;
	}
	// fordított eset
	if	(
			(kliensek->next->tipp==1 && kliensek->tipp==2) ||
			(kliensek->next->tipp==2 && kliensek->tipp==3) ||
			(kliensek->next->tipp==3 && kliensek->tipp==1)
		){
		strcpy(c2,"\nNem nyert.\n");
		strcpy(c1,"\nNyert!\n");
		kliensek->op++;
	}
	if	(
		kliensek->tipp == kliensek->next->tipp
			){
			strcpy(c1,"\nEgyenlő!\n");
			strcpy(c2,"\nEgyenlő!\n");
		}

	if (kliensek->tipp==0) { strcpy(c1,"\nNem nyert.\n"); }
	if (kliensek->next->tipp==0) { strcpy(c2,"\nNem nyert.\n"); }

	send(kliensek->cfd,c1,strlen(c1),0);
	send(kliensek->next->cfd,c2,strlen(c2),0);

	// új játék
	sendallclient("\nÚj játék: (i + Enter) Kilépés:(n + Enter)\n");
	struct kliens* kl=kliensek;
		while(kl!=NULL){
			int i=kl->cfd;
			pontkuldes(i,kl->op);
			kl=kl->next;
		}
}

void kliensTippnull(){ // játék kezdetekor a tippeket nullázni kell
	struct kliens* kl=kliensek;
	while(kl!=NULL){
		kl->tipp=0;
		kl=kl->next;
	}
}

void kliensinput(int i){
	int fogadottByteok=recv(i,socketInputBuffer,sizeof(socketInputBuffer),0);
	if(fogadottByteok<=0){
		klienstorles(i);
	}
	else{ //klienstől fogadott adat feldolgozása
		if ((strncmp(socketInputBuffer, "s", 1) == 0) && !jatek) {
			if (kliensszam==1)
				sendallclient("\nKell meg egy jatekos\n");
			if (kliensszam==2){
				jatek=1;
				sendallclient("\nKEZDODIK\n");
				kliensTippnull();
				gettimeofday(&gamestart, NULL);
				gettimeofday(&elozo, NULL);
			}
			return;
		}
		if ((strncmp(socketInputBuffer, "n", 1) == 0) && !jatek) { // n re kilép
			klienstorles(i);
			return;
		}
		if ((strncmp(socketInputBuffer, "i", 1) == 0) && !jatek) { // i re uj
			if (kliensszam==1)
				sendallclient("\nKell meg egy jatekos\n");
			if (kliensszam==2){
				jatek=1;
				sendallclient("\nKEZDODIK\n");
				kliensTippnull();
				gettimeofday(&gamestart, NULL); // jatek kezdet kell!!
				gettimeofday(&elozo, NULL);
			}
			return;
		}
		if ((strncmp(socketInputBuffer, "k", 1) == 0) && jatek) {
			struct kliens* kl=kliensek;
			while(kl->cfd!=i){ // elmegyunk a kliensig
				kl=kl->next;
			}
			if (kl->tipp==0)
				kl->tipp=1; // beirjuk a tippjet, szamot irunk oda, azt könnyebb hasonlítani
			else
			{
				char c[]="Mar tippeltel\n";
				send(i,c,sizeof c,0);
			}
			return;
		}
		if ((strncmp(socketInputBuffer, "p", 1) == 0) && jatek) {
			struct kliens* kl=kliensek;
			while(kl->cfd!=i){
				kl=kl->next;
			}
			if (kl->tipp==0)
				kl->tipp=2;
			else
			{
				char c[]="Mar tippeltel\n";
				send(i,c,sizeof c,0);
			}
			return;
		}
		if ((strncmp(socketInputBuffer, "o", 1) == 0) && jatek) {
			struct kliens* kl=kliensek;
			while(kl->cfd!=i){
				kl=kl->next;
			}
			if (kl->tipp==0)
				kl->tipp=3;
			else
			{
				char c[]="Mar tippeltel\n";
				send(i,c,sizeof c,0);
			}
			return;
		}


		char c[]="Nem ertem\n";
		send(i,c,sizeof c,0);


	}
}

void processLoop(){
	fd_set readfd;
	FD_ZERO(&readfd);
	struct sockaddr kliensaddr; //a kliens socket adatai ebbben lesznek

	while(1){
		tv.tv_sec = 1;
		tv.tv_usec = 0;
		readfd=master;
		select(fdmax+1,&readfd,NULL,NULL,&tv); //beesik egy kliens
		timer();
		for(i=0;i<=fdmax;i++){ //itt minden azonos az óraival
			if(FD_ISSET(i,&readfd)){
				if(i==listenSocket){
					socklen_t kliensaddrmeret=sizeof(kliensaddr);
					int new_fd=accept(listenSocket,&kliensaddr,&kliensaddrmeret); //itt akceptáljuk a klienst
					if (new_fd==-1)
						handle_error("accept"); // ha zűr volt leállunk

					if(kliensszam<kliensszamMAX){ // és itt kissé átszabtam a programot, sztem érthetőbb
						klienshozzaad(kliensaddr,new_fd);// oké, még fork on belül vagyunk a klienst simán addoljuk
					} //a kliensszam leptetodik ugye
					//es itt viszont mar vizsgaljuk mi is van a kliensszammal?
					else{ //elertuk a limitet: fork
						send(new_fd, "Nem férsz be.\n",
						strlen("Nem férsz be.\n"), 0);
						close(new_fd);

						//A fork egyenkőre kivéve

//						pid_t pid = fork();
//						if (pid==0){ // child vagyok
//							printf("\n%d csoport server PID-je mostantol: %d",group, getpid()); // ki is írjuk az új child PIDet
//							fflush(stdout);
//							FD_CLR(listenSocket,&master); //a listen socket nem kell
//							close(listenSocket);
//							struct kliens* kl=kliensek; // a klienseknek szépen elküldjük, hogy őket már más server szál szolgálja ki
//							while(kl!=NULL){
//								kl->PID=getpid();
//								msgtoKliens(kl->cfd,"\nserverPID: ",kl->PID);
//								kl=kl->next;
//							}
//						}
//						else{ // parent vagyok, fork volt, a dolgaimat tehát ki kell nullázni és újra gyártani
//							FD_ZERO(&master);
//							FD_ZERO(&readfd);
//							FD_SET(listenSocket,&master);
//							kliensszam=0;
//							struct kliens* kl1; // a klienseket is
//							while(kliensek!=NULL){
//								kl1=kliensek;
//								kliensek=kliensek->next;
//								close(kl1->cfd);
//								free(kl1);
//							}
//						}
//						group++; // na még megléptetjük a csoport azonosítót és készen is vagyunk új kliens felvételre
					}
				}
				else{
					kliensinput(i);
				}
			}
		}

	}

}


int main(){

	serverListen();

	processLoop();

	return 0;
}
